// @ts-check
import * as THREE from 'three'
import Bullet from './Bullet'
// import * as CANNON from 'cannon'

class Engine {
  constructor({
    canvas,
    map,
    onNext,
    controls,
    world
  }) {
    this._canvas = canvas
    this._onNext = onNext
    this._map = map
    this._world = world
    this._dt = 1/60
    this._controls = controls
    this._timestamp = Date.now()
    this._bullets = []
    this._players = []
    this._selfPlayer = null

    // renderer
    const renderer = new THREE.WebGLRenderer({
      canvas,
      antialias: true
    })
    renderer.shadowMapType = THREE.PCFSoftShadowMap
    this._renderer = renderer
    renderer.shadowMap.enabled = true
    const { width, height } = this._updateCanvasSize()
    window.addEventListener('resize', this._updateCanvasSize)
    renderer.setSize(window.innerWidth, window.innerHeight)

    // scene
    const scene = new THREE.Scene()
    this._scene = scene

    // init
    this._lighting()

    // helper
    const axesHelper = new THREE.AxesHelper(50)
    scene.add(axesHelper)

    // fog
    scene.fog = new THREE.Fog(0x000000, 0, 250)

    // map
    map.objects.forEach(obj => {
      scene.add(obj)
    })

    // fps camera
    const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000)
    this._camera1 = camera
    controls.addCamera(camera)
    this._currCamera = camera

    // top camera
    const camera2 = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000)
    this._camera2 = camera2
    camera2.position.x = 0
    camera2.position.y = 150
    camera2.position.z = 0
    camera2.rotateX(-Math.PI / 2)

    controls.onSwitchCamera(() => {
      this._currCamera = this._currCamera === this._camera1 ? this._camera2 : this._camera1
    })

    // add self
    scene.add(controls.object)

    // shoot listener
    controls.onShoot(this._handleShoot)
  }

  addSelf = player => {
    this._players.push(player)
    this._selfPlayer = player
  }

  addPlayer = (player) => {
    this._players.push(player)
  }

  _handleShoot = () => {
    const bullet = new Bullet({
      position: this._controls.position,
      rotation: this._controls.rotation,
    })
    this._bullets.push(bullet)
    this._scene.add(bullet.object)
    this._world.addBody(bullet.cannonBody)
  }

  _lighting = () => {
    const ambient = new THREE.AmbientLight('#dddddd')
    this._scene.add(ambient);

    const light = new THREE.SpotLight(0xffffff)
    light.position.set(0, 30, 0)
    light.target.position.set(0, 0, 0)
    if (true) {
      light.castShadow = true;

      light.shadow.camera.near = 20
      light.shadow.camera.far = 50
      light.shadow.camera.fov = 40
      light.shadow.bias = 0.1
      light.shadow.mapSize.width = 2 * 512
      light.shadow.mapSize.height = 2 * 512

      light.shadow.camera.visible = true
    }
    this._scene.add(light)
  }

  destroy = () => {
    window.removeEventListener('resize', this._updateCanvasSize)
  }

  _updateCanvasSize = e => {
    const width = window.innerWidth
    const height = window.innerHeight
    this._renderer.setSize(width, height)
    return {
      width,
      height
    }
  }

  _next = delta => {
    this._map.next(delta)
    this._controls.next(delta)
    this._bullets.forEach(b => b.next(delta))
    if (this._onNext) this._onNext(delta)
    if (this._controls._movingForward || this._controls._movingBackward || this._controls._movingLeft || this._controls._movingRight) {
      if (this._selfPlayer) {
        this._selfPlayer.walk()
      }
    } else {
      if (this._selfPlayer) {
        this._selfPlayer.idle()
      }
    }
    if (this._selfPlayer) {
      this._selfPlayer.exec()
      // this._selfPlayer.mesh.position.set(this._controls.position.x, this._controls.position.y, this._controls.position.z)
      // this._selfPlayer.mesh.scale.set(0.02, 0.02, 0.02)
    }
  }

  start = () => {
    const animate = () => {
      requestAnimationFrame(animate)
      this._world.step(this._dt)
      const delta = Date.now() - this._timestamp
      this._next(delta)
      this._timestamp = Date.now()
      this._renderer.render(this._scene, this._currCamera)
    }

    animate()
  }
}

export default Engine
